import multiprocessing as mp
import logging
import time
from Queue import Empty
import sys
import datetime
from misc import misc
import navalasystem as na
from misc.parts import Sleeper


class NavalaProcess(mp.Process, Sleeper):
    """Implements a series of add-ons to multiprocessing.Process.
    
    the run_() method should be implemented (not run()!)
    
    Communication is done through "messages". A message consists of a token, data
    pair. PutMessage() is used to put an arbitrary message.
    
    NavalaProcess treats the following tokens inside TreatQueueItem():
        reinitiate
        exit
        ping
        
    The following messages may be sent through outQueue:
        pong -- response to ping
        error -- In case an exception is raised inside run_()
        
    Registering and unregistering into na.vala.pm is done automatically.
    
    """
    ## Setup Variables
    # To compose log messages
    dash = "--------" 
    # Set this to send messages to the Process Manager
    outQueue = None
    # Process id
    id = -1
    # Whether to 

    ## State variables used when process is running
    # Don't set this directly, this is made inside TreatQueueItem()
    flagExit = False
    # Don't set this directly, this is made inside TreatQueueItem()
    flagReinitiate = False

    ## Communication variables
    # This is initialized internally
    inQueue = None

    def __init__(self, outQueue=None, *args, **kwargs):
        mp.Process.__init__(self, *args, **kwargs)
        self.logFilename = misc.slugify("p_"+self.name)+".log"
        logging.info("File name is %s" % self.logFilename)
        self.inQueue = mp.Queue()
        self.__flagStartCalled = False # Protects from start() being called by mistake
        
    ## /\ Called from parent process
    ###########################################################################
    ## \/ Called from child process
    
    def PutMessage(self, token, msg=True):
        """Puts an arbitrary message. To be called by parent process."""
        if not token[0].isupper():
            raise RuntimeError("Invalid token: ``%s`` (must be CamelCase with first letter uppercase)" % token)
        self.inQueue.put((token, msg))
    
    def Exit(self):
        """Puts an "exit" message. To be called by parent process."""
        logging.info("%s (%s): Exit()" % (self.name, self.__class__.__name__))
        self.PutMessage("Exit")
        
    def Reinitiate(self):
        """Puts a "reinitiate" message. To be called by parent process."""
        logging.info("%s (%s): Reinitiate()" % (self.name, self.__class__.__name__))
        self.PutMessage("Reinitiate")
        
    def Ping(self, msg="hi"):
        """Puts a "ping" message. To be called by parent process."""
        self.PutMessage("Ping", msg)
    
    def ReadQueue(self):
        """Include a call to this function in the reimplementation of run_()."""
        flagEmpty = False
        while not flagEmpty:
            try:
                t = self.inQueue.get(False)
                self.TreatQueueItem(t)
            except Empty:
                flagEmpty = True
                
    def SendMessage(self, token, data=True):
        """Sends message through outQueue, if it is set."""
        if not token[0].isupper():
            raise RuntimeError("Invalid token: ``%s`` (must be CamelCase with first letter uppercase)" % token)
        if self.outQueue:
            self.outQueue.put((self.id, token, data))
        else:
            self.logger.warning("Cannot send \"%s\", outQueue not set" % token)

    def TreatQueueItem(self, t):
        """Treats messages got from inQueue. Reimplement _TreatQueueItem()."""
        if not isinstance(t, tuple):
            t = (t, True)
        self.logger.info("Received message \"%s\" with data=%s" % (t[0], repr(t[1])))
        self._TreatQueueItem(t[0], t[1])
        

    def _TreatQueueItem(self, token, data):
        """Treats "exit", "reinitiate", and "ping". Reimplement to treat more tokens.
        
        This method should be reimplemented to treat additional tokens. In the
        reimplementation, call the superclass method whenever the token is left
        untreated"""
        if token == "Exit":
            self.flagExit = data
        elif token == "Reinitiate":
            self.flagReinitiate = data
        elif token == "Ping":
            self.SendMessage("Pong", "You said \"%s\"" % data)
            self.logger.info("PINGED: %s" % data)
        else:
            self.logger.warning("Unknown token: \"%s\"" % token)
        
    def InitLogger(self):
        self.logger = misc.GetLogger(self.name, self.logFilename, False)
    
    def Start(self, slot=None):
        """Registers automatically into na.vala.pm and calls Process.start().
        
        Arguments:
            slot=None -- Registers slot to receive signals emitted by the
                         process monitor when it receives a message from the
                         process which it doesn't handle.
            """
        na.vala.pm.process_Register(self)
        if slot is not None:
            na.vala.pm.process_RegisterSlot(self, slot)
        self.__flagStartCalled = True
        self.start()
        
    def run(self):
        "Does a series of initializations (including na.vala and calls run_()"""
        if not self.__flagStartCalled:
            raise RuntimeError("call Start(), not start()!")
        self.InitLogger()        
        self.logger.info("")
        self.logger.info("%s Process \"%s\" started at %s %s" % (self.dash, 
            self.name, datetime.datetime.today().strftime("%Y/%m/%d %H:%M"), self.dash))
            
        na.vala = na.System() # Overwrites system from main process
        na.vala.flagMaster = False
        na.vala.Startup() # Starts a new System
        
        try: # Puts run_() in a try...except block to log uncaught exceptions
            self.run_()
        except Exception as E:
            self.logger.exception(E)
            self.SendMessage("Error", E)
            self.logger.info("Finished process with an error")
        finally:
            self.logger.info("Will tell process monitor to unregister")
            self.SendMessage("Unregister")
            

    def run_(self):
        """Reimplement this method (not run()!)*
       
        *A few things to be taken into account in the reimplementation:
            - Include a call to ReadQueue() in the process loop
            - Act upon flagReinitiate and set it to false,
            - Act on flagExit (break, probably)
        """
        pass
